package us.kg.kotoriforest.spring.ioc;

import us.kg.kotoriforest.spring.annotation.Bean;
import us.kg.kotoriforest.spring.annotation.DI;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

/**
 * @author Kotori rotroutine@163.com
 * @since 2025/1/5
 */
public class AnnotationApplicationContext implements ApplicationContext {
    // 创建的IOC容器全部放在map容器中
    private final Map<Class<?>, Object> beans = new HashMap<>();
    // 包绝对路径的前缀 即绝对路径中相同的部分
    private String packageAbsolutePathPrefix;

    /**
     * 将{@code basePackage}及其子包中所有带有{@code @Bean}注解的类实例化
     *
     * @param basePackage 需要加载的包名
     */
    public AnnotationApplicationContext(String basePackage) {
        // us.kg.kotoriforest.spring.dao.impl

        // 每一个点在文件目录中都代表着/ 用来划分层级 所以我们需要先将.换成/
        String basePackagePath = basePackage.replace('.', '/');

        // 我们需要得到编译后包的绝对路径 而通过File类得到的绝对路径是源文件的绝对路径 我们可以通过类加载器来得到编译后文件的绝对路径
        try {
            Enumeration<URL> resources = Thread.currentThread().getContextClassLoader().getResources(basePackagePath);
            while (resources.hasMoreElements()) {
                URL url = resources.nextElement();
                String filePath = URLDecoder.decode(url.getFile(), StandardCharsets.UTF_8);

                // 截取绝对路径的共同部分
                packageAbsolutePathPrefix = filePath.substring(0, filePath.length() - basePackagePath.length());

                // 扫描包创建对象
                loadBean(new File(filePath));
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        loadDI();
    }

    private void loadDI() {
        for (Object bean : beans.values()) {
            Class<?> beanClass = bean.getClass();

            for (Field field : beanClass.getDeclaredFields()) {
                DI di = field.getDeclaredAnnotation(DI.class);
                if (null != di) {
                    field.setAccessible(true);
                    try {
                        field.set(bean, beans.get(field.getType()));
                    } catch (IllegalAccessException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }
    }

    /**
     * 执行包扫描 创建对象
     *
     * @param file 扫描的根目录
     */
    private void loadBean(File file) {
        // 如果不是文件夹就不扫描了
        if (!file.isDirectory()) return;

        // 获取文件夹中的所有内容
        File[] files = file.listFiles();

        // 如果文件夹为空就返回
        if (null == files) return;

        for (File f : files) {
            // 子文件夹就继续递归
            if (f.isDirectory()) loadBean(f);
            else {
                String pathWithClass = f.getAbsolutePath().substring(packageAbsolutePathPrefix.length() - 1);

                // 判断文件是否以.class结尾
                if (pathWithClass.endsWith(".class")) {
                    // 将路径中的\换成. 将后缀去掉
                    String className = pathWithClass.replace('\\', '.').replaceAll(".class", "");

                    try {
                        Class<?> aClass = Class.forName(className);
                        // 判断该类是不是接口
                        if (aClass.isInterface()) continue;

                        // 判断类上有没有 @Bean 注解
                        if (aClass.isAnnotationPresent(Bean.class)) {
                            // 实例化对象
                            Constructor<?> constructor = aClass.getDeclaredConstructor();
                            constructor.setAccessible(true);
                            Object bean = constructor.newInstance();

                            // 如果该类有接口 就让接口作为key值
                            if (aClass.getInterfaces().length > 0) {
                                beans.put(aClass.getInterfaces()[0], bean);
                            } else {
                                beans.put(aClass, bean);
                            }
                        }
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }
    }

    @Override
    public Object getBean(Class<?> clazz) {
        return beans.get(clazz);
    }

}
